﻿/*
 * Cratos Server - Entity Access Layer (EAL)
 * Cratos EAL class
 * 
 * Copyright (c) 2011 Draco
 * 
 * This file is part of Cratos.
 * 
 * Cratos is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * Cratos is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Cratos.  If not, see <http://www.gnu.org/licenses/>.
 * 
 *       .==.        .==.
 *      //'^\\      //^'\\
 *     // ^ ^\(\__/)/^ ^^\\
 *    //^ ^^ ^/6  6\ ^^ ^ \\
 *   //^ ^^ ^/( .. )\^ ^ ^ \\
 *  // ^^ ^/\| V""V|/\^ ^ ^ \\
 * //^^/\/ /  '~~'  \ \/\^ ^ \\
 *  ------------------------------------
 *  HERE BE DRAGONS
 *  
 *  Johan Dercksen - johan@draco.co.za
 */
using System;
using System.Linq;
using System.Text;
using System.Data;
using System.Reflection;
using System.Configuration;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using Microsoft.Practices.Unity;

using Cratos.Tools.General;
using Cratos.Tools.DataObject;

namespace Cratos.Server.Entity
{
	/// <summary>
	/// Cratos Entity Access Layer (EAL) class
	/// </summary>
	public class CratosEAL : CratosEALBase, ICratosEntityAccess, IDisposable
	{
		#region Class Constant declarations
		#endregion

		#region Class Event declarations
		#endregion

		#region Class Variable declarations
		private bool m_IsDisposed;
		#endregion

		#region Class Constructors & Destructors
		/// <summary>
		/// Class Constructor
		/// </summary>
		public CratosEAL() : base() { }

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or
		/// resetting unmanaged resources.
		/// </summary>
		public new void Dispose() { Dispose(true); GC.SuppressFinalize(this); }

		/// <summary>
		/// Dispose method doing the disposing
		/// </summary>
		/// <param name="isDisposing"></param>
		protected new virtual void Dispose(bool isDisposing)
		{
			// Declare the required variables

			// Check to see if Dispose has already been called.
			if (!this.m_IsDisposed)
			{
				// Dispose the objects if required
				if (isDisposing)
				{
					// Dispose of the required objects
				}
			}

			// Set the disposed indicator
			this.m_IsDisposed = true;

			// Return to calling method
			return;
		}
		#endregion

		#region Class Properties
		#endregion

		#region Class Methods
		/// <summary>
		/// Entity Perform Action
		/// </summary>
		/// <returns>A Boolean indicating success of the method</returns>
		public bool PerformAction(ref IDOCratos cratosDO)
		{
			// Declare the required variables
			bool														returnValue				= false;
			DOCratosActionAttribute					doActionAttribute	= null;
			DOCratosActionColumnAttribute[] actionAttributes;

			try
			{
				// Reset all the relevant values
				this.Errors.Clear();

				// Make sure we are performing an action
				if (cratosDO.DOAction == DOCratosAction.None) { return(true); }

				// Save the relevant values
				this.DataObject						= cratosDO;
				this.DataObject.IsLoading	= true;

				// Validate the Data Object
				if (cratosDO.Validate())
				{
					// Setup the DB Connection
					if (this.SetupDBConnection())
					{
						// Clear all pending Paramaters
						this.DBAccess.ClearParameters();

						// Process all the Child Dependant embedded objects
						if (this.DataObject.DOAction != DOCratosAction.Retrieve) { this.ProcessEmbedded(false, this.DataObject.DOAction); }

						#region Command Type and Text processing
						// Set the relevant Command Options
						this.DBAccess.DBActionType = this.DataObject.DBActionType;
						this.DBAccess.DBActionText = "";

						// Process all the Commands
						foreach (DOCratosActionAttribute currentAction in this.DataObject.AllDOActions)
						{
							// Look for the Retrieve All Command
							if ((currentAction.CratosAction == this.DataObject.DOAction) && (currentAction.CratosActionType == this.DataObject.DBActionType))
							{
								doActionAttribute          = currentAction;
								this.DBAccess.DBActionText = currentAction.ActionString;
								break;
							}
						}

						// Make sure we have an Action to work with
						if (doActionAttribute == null)
						{
							throw new Exception(string.Format("Action Attribute not specified ({0} - {1}", cratosDO.GetType(), cratosDO.DOAction));
						}
						#endregion

						#region Parameter Processing
						// Process all the Columns
						foreach (DOCratosColumnAttribute currentColumn in this.DataObject.AllDOColumns)
						{
							// Make sure we have a Create Attribute on the column
							actionAttributes = (DOCratosActionColumnAttribute[])this.DataObject.GetType().GetProperty(currentColumn.Property).GetCustomAttributes(typeof(DOCratosActionColumnAttribute), false);

							// Process all the Attributes
							foreach (DOCratosActionColumnAttribute currentAttribute in actionAttributes)
							{
								// Make sure it is a Retrieve attribute
								if (currentAttribute.CratosAction == this.DataObject.DOAction)
								{
									// Add the SP Parameter
									this.AddParameter(string.IsNullOrEmpty(currentAttribute.DBParameterName) ? "@" + currentColumn.DBColumnName : currentAttribute.DBParameterName,
																		currentColumn.DbType,
																		this.DataObject.GetType().GetProperty(currentColumn.Property).GetValue(this.DataObject, null),
																		currentAttribute.IsMandatory, false, false, SqlDbType.Binary);
								}
							}
						}
						#endregion

						#region Execute the stored procedure and process results
						// Set the default Action Result
						this.DataObject.DOActionResult = DOCratosActionResult.Error;

						// Handle the relevant output if expected
						if (doActionAttribute.IsExpectOutput)
						{
							#region Action Expects output
							// Execute the Action
							this.DBAccess.ExecuteReader();

							// Handle any errors
							if (this.DBAccess.Result == CratosDatabaseResult.Success)
							{
								// Set the Action Result
								this.DataObject.DOActionResult = DOCratosActionResult.NoRecords;

								// Make sure we have data to work with
								if (this.DBAccess.ReaderHasRows)
								{
									// Populate the Data Object
									this.LoadDataFromDataReader((DOCratos) this.DataObject);

									// Update the relevant object(s)
									this.DataObject.DOActionResult = DOCratosActionResult.Success;
									this.DataObject.IsLoaded       = true;

									// Process all the embedded objects (None and Parent Dependant)
									this.ProcessEmbedded(true, this.DataObject.DOAction);
								}
							}
							#endregion
						}
						else
						{
							#region No Output Expected
							// Execute the Action
							this.DBAccess.ExecuteNonQuery();

							// Update the Action Result
							this.DataObject.DOActionResult = DOCratosActionResult.Success;

							// Process all the embedded objects (None and Parent Dependant)
							this.ProcessEmbedded(true, this.DataObject.DOAction);
							#endregion
						}
						#endregion
					}
				}
				else
				{
					// Process the relevant errors
					foreach (CratosError currentError in cratosDO.Errors) { this.Errors.Add(currentError); }
				}
			}
			catch (Exception runtimeException)
			{
				// Create and setup a new error
				this.Errors.Add(new CratosError("CratosEAL.PerformAction", CratosLogSeverity.Exception,
																				"Runtime error ocurred", runtimeException));
			}
			finally
			{
				// Update the return value
				returnValue = (this.Errors.Count == 0);

				// Reset the relevant values
				if (this.DataObject != null)
				{
					this.DataObject.IsLoading = false;
					this.DataObject.DOAction  = DOCratosAction.None;
				}
			}

			// Return to calling method
			return(returnValue);
		}
		#endregion

		#region Class Support Methods
		private bool ProcessEmbedded(bool isPostProcessing, DOCratosAction parentAction)
		{
			// Declare the required variables
			bool														returnValue = false;
			object													sourceData;
			object													embeddedCollection;
			IDOCratos												embeddedObject;
			DOCratosEmbeddedKeyAttribute[]	embeddedKeyAttributes;

			try
			{
				// Process all the embedded objects
				foreach (DOCratosEmbeddedAttribute currentEmbedded in this.DataObject.AllDOEmbedded)
				{
					// Make sure we should process this Embedded object
					if ((isPostProcessing && (parentAction == DOCratosAction.Retrieve)) ||
							(isPostProcessing && (parentAction != DOCratosAction.Retrieve) && (currentEmbedded.Dependancy != DOCratosDependancy.Child)) ||
							(!isPostProcessing && (parentAction != DOCratosAction.Retrieve) &&
								((currentEmbedded.Dependancy != DOCratosDependancy.None) && (currentEmbedded.Dependancy != DOCratosDependancy.Parent)))
						 )
					{
						// Retrieve the relevant attributes
						embeddedKeyAttributes = (DOCratosEmbeddedKeyAttribute[])this.DataObject.GetType().GetProperty(currentEmbedded.Property).GetCustomAttributes(typeof(DOCratosEmbeddedKeyAttribute), false);

						// Handle the Single / collection Embedded object
						if (currentEmbedded.IsCollection)
						{
							#region Collection Handling
							// Retrieve the object and associated Methods and Properties
							embeddedCollection = DOCratosHelper.GetPropertyValue(this.DataObject, currentEmbedded.Property);

							// Update the relevant Properties
							DOCratosHelper.SetPropertyValue(embeddedCollection, "IgnoreUnity", this.IgnoreUnity);
							DOCratosHelper.SetPropertyValue(DOCratosHelper.GetPropertyValue(embeddedCollection, "DataObject"), 
																							"DBConnectionName", this.DataObject.DBConnectionName);

							// Perform the relevant action if required
							if (((DOCratosCollectionAction)DOCratosHelper.GetPropertyValue(embeddedCollection, "CollectionAction")) != DOCratosCollectionAction.None)
							{
								#region Parameter / Filter Processing
								// Set the relevant Embedded Key data
								foreach (DOCratosEmbeddedKeyAttribute currentKey in embeddedKeyAttributes)
								{
									// Retrieve the relevant Source Data
									sourceData = this.DataObject.GetType().GetProperty(currentKey.ParentProperty).GetValue(this.DataObject, null);

									// Update the Key Property
									DOCratosHelper.SetPropertyValue(DOCratosHelper.GetPropertyValue(embeddedCollection, "DataObject"), 
																									currentKey.EmbeddedKeyProperty, sourceData);
								}
								#endregion

								// Send the request and handle any errors
								if (!((bool)DOCratosHelper.InvokeMethod(embeddedCollection, "PerformAction", null)))
								{
									foreach (CratosError currentError in ((IEnumerable<object>)DOCratosHelper.GetPropertyValue(embeddedCollection, "Errors")))
									{
										this.Errors.Add(currentError);
									}
								}
							}
							#endregion
						}
						else
						{
							#region Single Object Handling
							// Retrieve the object
							embeddedObject							    = (IDOCratos)this.DataObject.GetType().GetProperty(currentEmbedded.Property).GetValue(this.DataObject, null);
							embeddedObject.IgnoreUnity	    = this.IgnoreUnity;
							embeddedObject.DBConnectionName = this.DataObject.DBConnectionName;

							// Perform the relevant action if required
							if (embeddedObject.DOAction != DOCratosAction.None)
							{
								#region Parameter / Filter Processing
								// Set the relevant Embedded Key data
								foreach (DOCratosEmbeddedKeyAttribute currentKey in embeddedKeyAttributes)
								{
									// Retrieve the relevant Source Data
									sourceData = this.DataObject.GetType().GetProperty(currentKey.ParentProperty).GetValue(this.DataObject, null);

									// Update the Key Property
									embeddedObject.GetType().GetProperty(currentKey.EmbeddedKeyProperty).SetValue(embeddedObject, sourceData, null);
								}
								#endregion

								// Send the request and handle any errors
								if (!embeddedObject.PerformAction()) { foreach (CratosError currentError in embeddedObject.Errors) { this.Errors.Add(currentError); } }
							}
							#endregion
						}
					}
				}
			}
			catch (Exception runtimeException)
			{
				// Create and setup a new error
				this.Errors.Add(new CratosError("CratosEAL.ProcessEmbedded", CratosLogSeverity.Exception,
																				"Runtime error ocurred", runtimeException));
			}
			finally
			{
				// Update the return value
				returnValue = (this.Errors.Count == 0);
			}

			// Return to calling method
			return (returnValue);
		}
		#endregion
	}
}
